package org.zoomdev.zoom.ioc.impl.factories;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.common.filter.Filter;
import org.zoomdev.zoom.common.utils.CachedClasses;
import org.zoomdev.zoom.common.utils.Classes;
import org.zoomdev.zoom.ioc.Ioc;
import org.zoomdev.zoom.ioc.IocConstructor;
import org.zoomdev.zoom.ioc.IocException;
import org.zoomdev.zoom.ioc.IocFactory;
import org.zoomdev.zoom.ioc.IocKey;
import org.zoomdev.zoom.ioc.IocTypeKey;
import org.zoomdev.zoom.ioc.IocUtils;
import org.zoomdev.zoom.ioc.annotations.IocBean;
import org.zoomdev.zoom.ioc.annotations.Module;
import org.zoomdev.zoom.ioc.annotations.ModuleInit;
import org.zoomdev.zoom.ioc.modules.CoreIocModule;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;

/**
 * @IocBean factory
 */
public final class ModuleIocFactory implements IocFactory, IocFactory.IocKeyContainer {


    private static final Logger log = LoggerFactory.getLogger(CoreIocModule.class);
    private final HashMap<IocKey, ModuleIocConstructor> configs = new HashMap<>();
    private final HashMap<IocKey, IocConstructor> typeVariables = new HashMap<>();
    private final Ioc ioc;
    List<Class<?>> initClass = new ArrayList<>();
    List<Class<?>> beforeInitClasses = new ArrayList<>();
    List<Class<?>> beanModules = new ArrayList<>();
    List<MethodInfo> initMethods = new ArrayList<>();
    private Executor executor;
    public ModuleIocFactory(Ioc ioc) {
        this.ioc = ioc;
    }

    public Map<IocKey, ModuleIocConstructor> getConfigs() {
        return configs;
    }

    public List<Class<?>> getBeanModules() {
        return beanModules;
    }

    public void setBeanModules(List<Class<?>> beanModules) {
        this.beanModules = beanModules;
    }

    public void setExecutor(Executor executor) {
        this.executor = executor;
    }

    @Override
    public Collection<IocKey> find(Class type) {
        List<IocKey> list = new ArrayList<>(3);
        for (Map.Entry<IocKey, ModuleIocConstructor> entry : configs.entrySet()) {
            IocKey key = entry.getKey();
            ModuleIocConstructor constructor = entry.getValue();
            if (constructor.realType == type || key.getType() == type) {
                list.add(key);
            }
        }
        if (list.size() == 0) {
            return null;
        }
        return list;
    }

    public void parseModules(List<Class<?>> types) {
        Ioc ioc = this.ioc;

        ioc.put(new IocTypeKey(Module.class), types);
        ioc.put(new IocTypeKey(Module[].class), types);

        parseFactories(types);
        initModules(types);
    }

    /**
     * 预先执行相关Module的初始化方法
     */
    public void execute(Ioc ioc, Filter<MethodInfo> filter) {
        Iterator<MethodInfo> it = initMethods.iterator();
        while (it.hasNext()) {
            MethodInfo info = it.next();
            Method method = info.method;
            assert (method != null);
            if (filter.accept(info)) {
                info.execute(ioc);
                it.remove();
            }
        }
    }

    public List<MethodInfo> remove(Filter<MethodInfo> filter) {
        List<MethodInfo> infos = new ArrayList<>();
        Iterator<MethodInfo> it = initMethods.iterator();
        while (it.hasNext()) {
            MethodInfo info = it.next();
            if (filter.accept(info)) {
                infos.add(info);
                it.remove();
            }
        }
        return infos;
    }

    @Override
    public IocConstructor createConstructor(IocKey key) {
        if (key.getType() == null) {
            return null;
        }
        IocConstructor constructor = configs.get(key);
        if (constructor == null) {
            if (IocUtils.isGenericType(key.getType())) {
                //尝试降维查找
                IocUtils.GenericType gt = (IocUtils.GenericType) key.getType();
                Type rawType = gt.getRawType();
                IocKey newKey = new IocTypeKey(rawType, key.getName());
                constructor = typeVariables.get(newKey);
            }
        }
        return constructor;
    }

    /**
     * 将含有IocBean的方法解析出来，做一些校验
     *
     * @param types
     */
    protected void parseFactories(List<Class<?>> types) {

        for (Class<?> type : types) {
            try {
                parseFactory(type);
            } catch (Exception e) {
                throw new ZoomException("解析Module " + type + " 的IocBean方法的时候发生异常", e);
            }

        }
    }

    protected void initModules(List<Class<?>> types) {
        final Ioc ioc = this.ioc;

        for (Class<?> type : types) {
            parseModuleInit(type, ioc);
        }

        synchronized (ioc) {
            for (Class<?> type : beforeInitClasses) {
                Object obj = ioc.fetch(type);
                assert (obj instanceof BeforeModuleInitialize);
                if (log.isDebugEnabled()) {
                    log.debug("BeforeModuleInitialize :" + type);
                }
                ((BeforeModuleInitialize) obj).beforeInitialize(ioc);
            }

            for (Class<?> type : initClass) {
                Object obj = ioc.fetch(type);
                assert (obj instanceof ModuleInitialize);
                if (log.isDebugEnabled()) {
                    log.debug("ModuleInitialize :" + type);
                }
                ((ModuleInitialize) obj).initialize(ioc);
            }

            List<MethodInfo> afterList = new ArrayList<>();
            List<MethodInfo> asyncList = new ArrayList<>();
            for (MethodInfo methodInfo : initMethods) {
                if (methodInfo.async) {
                    asyncList.add(methodInfo);
                    continue;
                }
                if (methodInfo.after) {
                    afterList.add(methodInfo);
                    continue;
                }
                methodInfo.execute(ioc);
            }


            for (MethodInfo methodInfo : afterList) {
                methodInfo.execute(ioc);
            }

            if (executor != null) {
                for (MethodInfo methodInfo : asyncList) {
                    executor.execute(() -> methodInfo.execute(ioc));
                }
            } else {
                for (MethodInfo methodInfo : asyncList) {
                    methodInfo.execute(ioc);
                }
            }

        }
    }

    private void parseModuleInit(Class<?> type, Ioc ioc) {
        if (Classes.hasInterface(type, ModuleInitialize.class)) {
            initClass.add(type);
        }
        if (Classes.hasInterface(type, BeforeModuleInitialize.class)) {
            beforeInitClasses.add(type);
        }


        for (Method method : CachedClasses.getPublicMethods(type)) {
            if (!method.isAnnotationPresent(ModuleInit.class)) {
                continue;
            }
            ModuleInit init = method.getAnnotation(ModuleInit.class);
            initMethods.add(new MethodInfo(ioc, method, type, init.after(), init.async()));
        }
    }

    /**
     * 将IocBean方法解析出来
     *
     * @param type
     */
    protected void parseFactory(Class<?> type) {
        try {
            int size = 0;
            for (Method method : CachedClasses.getPublicMethods(type)) {
                IocBean bean = method.getAnnotation(IocBean.class);
                if (bean == null) {
                    continue;
                }
                Type returnType = method.getGenericReturnType();
                if (returnType == void.class || returnType == Void.class) {
                    log.warn("IocBean标注的方法必须有能在Ioc容器的注册返回值");
                    continue;
                }
                if (returnType instanceof TypeVariable) {
                    throw new IocException("暂不支持纯泛型类型的模块构造函数,请尝试 SomeClass<T>");
                }

                ++size;
                if (!IocUtils.containTypeParameters(returnType)) {
                    returnType = IocUtils.createGenericType(returnType);
                    //同一个key会被覆盖掉
                    IocKey key = new IocTypeKey(returnType, bean.name());
                    configs.put(key, new ModuleIocConstructor(method, type));
                } else {
                    //降一个级别
                    returnType = method.getReturnType();
                    IocKey key = new IocTypeKey(returnType, bean.name());
                    TypeParameterIocConstructor constructor = new TypeParameterIocConstructor();
                    constructor.add(new MethodConfig(method, type));
                    typeVariables.put(key, constructor);
                }
            }
            if (size > 0) {
                beanModules.add(type);
            }
        } catch (Throwable e) {
            log.error("创建模块的构造方法产生异常,模块: " + type, e);
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            }
            throw new ZoomException(e);
        }
    }

    public interface ModuleInitialize {
        void initialize(Ioc ioc);
    }


    public interface BeforeModuleInitialize {
        void beforeInitialize(Ioc ioc);
    }


    public interface AfterModuleInitialize {
        void afterInitialize(Ioc ioc);
    }

    public interface IocConstructorProxy {
        Object newInstance(ModuleIocConstructor constructor, IocKey resultKey, Object[] args) throws Throwable;

        Class getRealType();
    }

    /**
     * @IocBean 方法的配置
     */
    public class Config {
        private final Method method;

        private final String desc;

        /**
         * 方法的返回值
         */
        private Class realType;

        public Config(Method method) {
            this.method = method;
            this.desc = org.objectweb.asm.Type.getMethodDescriptor(method);
        }

        public Class getRealType() {
            return realType;
        }

        public void setRealType(Class realType) {
            this.realType = realType;
        }
    }

    public class MethodInfo {
        private final Method method;
        private final Class type;
        private final IocKey[] keys;

        private final Ioc ioc;

        private final boolean after;

        private final boolean async;

        public MethodInfo(Ioc ioc, Method method, Class type, boolean after, boolean async) {
            assert (method != null);
            assert (type != null);
            this.ioc = ioc;
            this.method = method;
            this.type = type;
            this.keys = IocKey.valuesFromMethod(method);
            this.after = after;
            this.async = async;
        }

        /**
         * 是否依赖type
         *
         * @param key
         * @return
         */
        public boolean hasDependence(IocKey key) {
            Set<IocKey> sets = new HashSet<>();
            for (IocKey i : keys) {
                Collection<IocKey> collections = ioc.getDependencies(i);
                sets.addAll(collections);
            }

            return sets.contains(key);

        }

        public Class<?>[] getParameterTypes() {
            return method.getParameterTypes();
        }

        public void execute(Ioc ioc) {
            try {
                Object target = ioc.fetch(type);
                Object[] args = IocKey.getValues(ioc, keys);

                if (log.isDebugEnabled()) {
                    log.debug("Invoke module init method: " + method);
                    Class[] types = method.getParameterTypes();
                    for (int i = 0; i < types.length; ++i) {
                        Class type = types[i];
                        if (args[i] == null) {
                            continue;
                        }
                        Class targetClass = args[i].getClass();
                        if (!type.isAssignableFrom(targetClass) && !Classes.isWapClass(targetClass, type)
                                && !Classes.isWapClass(type, targetClass)) {
                            throw new ZoomException("非继承类" + type + ":" + targetClass);
                        }
                    }
                }
                method.invoke(target, args);
            } catch (Throwable e) {
                throw new IocException("IocBean创建出错，调用对应的方法:" + method, Classes.getCause(e));
            }
        }
    }

    /**
     * 泛型类
     */
    public class TypeParameterIocConstructor implements IocConstructor {

        private final List<MethodConfig> list = new ArrayList<>();

        public void add(MethodConfig config) {
            list.add(config);
        }

        @Override
        public IocKey[] getKeys(IocKey resultKey) {
            assert (resultKey.getType() instanceof IocUtils.GenericType);
            IocUtils.GenericType type = (IocUtils.GenericType) resultKey.getType();
            for (MethodConfig config : list) {
                if (config.match(type)) {
                    //取出来
                    return IocKey.fromMethod(resultKey, config.method);
                }
            }

            throw new IocException("不支持的类型" + resultKey);
        }

        @Override
        public Object newInstance(IocKey resultKey, Object[] args) throws Throwable {
            IocUtils.GenericType type = (IocUtils.GenericType) resultKey.getType();
            for (MethodConfig config : list) {
                if (config.match(type)) {
                    //取出来
                    Object module = ioc.fetch(config.moduleType);
                    return config.method.invoke(module, args);
                }
            }
            throw new IocException("这不可能");
        }
    }

    public class MethodConfig {

        Method method;

        /**
         * 模块类型
         */
        Class moduleType;


        ParameterizedType returnType;

        Type[] typeArguments;

        public MethodConfig(Method method, Class moduleType) {
            Type type = method.getGenericReturnType();
            assert (type instanceof ParameterizedType);
            this.returnType = (ParameterizedType) type;
            this.typeArguments = this.returnType.getActualTypeArguments();
            this.method = method;
            this.moduleType = moduleType;
        }

        /**
         * 只有可能是这个
         *
         * @param type
         * @return
         */
        public boolean match(IocUtils.GenericType type) {
            //主类就不用看了
            Type[] src = type.getActualTypeArguments();
            return match(this.typeArguments, src);
        }

        private boolean match(Type[] dest, Type[] src) {
            if (src.length != dest.length) {
                return false;
            }

            for (int i = 0; i < src.length; ++i) {
                Type s = src[i];
                Type d = dest[i];
                if (!match(d, s)) {
                    return false;
                }
            }

            return true;
        }

        /**
         * src是否与dest匹配
         *
         * @param src
         * @param dest
         * @return
         */
        private boolean match(Type dest, Type src) {
            if (dest instanceof TypeVariable) {
                return true;
            }

            if (dest instanceof ParameterizedType) {
                if (src instanceof ParameterizedType) {
                    if (((ParameterizedType) dest).getRawType() != ((ParameterizedType) src).getRawType()) {
                        return false;
                    }
                    return match(((ParameterizedType) dest).getActualTypeArguments(), ((ParameterizedType) src).getActualTypeArguments());
                } else {
                    return false;
                }
            }

            return dest == src;
        }


    }

    /**
     * 实际类
     */
    public class ModuleIocConstructor implements IocConstructor {

        private final IocKey[] keys;
        IocConstructorProxy proxy;
        private Method method;

        private Class realType;
        /**
         * 模块的class
         */
        private Class moduleType;

        public ModuleIocConstructor(Method method, Class moduleType) {
            this.method = method;
            this.moduleType = moduleType;
            this.keys = IocKey.fromMethod(method);
        }

        public Method getMethod() {
            return method;
        }

        public Class getModuleType() {
            return moduleType;
        }

        @Override
        public String toString() {
            return "ModuleIocConstructor:" +
                    method;
        }


        @Override
        public IocKey[] getKeys(IocKey resultKey) {
            return this.keys;
        }


        public void setType(Class type) {
            if (type != this.moduleType) {
                //更换了type，那么原来的method也要换
                try {
                    Method newMethod = type.getDeclaredMethod(method.getName(),
                            method.getParameterTypes());
                    this.method = newMethod;
                } catch (Throwable e) {
                    throw new IocException("无法更换模块类型: " + this.moduleType + "==>" + type, Classes.getCause(e));
                }
                this.moduleType = type;
            }
        }

        @Override
        public Object newInstance(IocKey resultKey, Object[] args) throws Throwable {
            if (proxy != null) {
                return proxy.newInstance(this, resultKey, args);
            }
            return newInstance0(args);
        }

        public Object newInstance0(Object[] args) throws Throwable {
            /**
             * 获取模块实例
             */
            Object module = ioc.fetch(moduleType);
            return method.invoke(module, args);
        }

        public void setProxy(IocConstructorProxy proxy) {
            this.proxy = proxy;
            this.realType = proxy.getRealType();
        }


        public Class getRealType() {
            return realType;
        }
    }
}
